package org.poptest.testng;

import org.poptest.bootstrap.Bootstrapper;
import org.poptest.bootstrap.BootstrapperFactory;
import org.poptest.model.Context;
import org.poptest.bootstrap.ParallelMode;
import org.poptest.util.AssertInternal;
import org.testng.*;

import java.util.HashMap;
import java.util.Map;

/**
 * @author Alan Roche
 *         Date: 03/02/11
 *         Time: 20:52
 */
public class BootstrapListener extends TestListenerAdapter implements IInvokedMethodListener {
    private Object lockObject = new Object();
    private boolean effectiveInstance;
    private static boolean effectiveInstanceInitialized;

    private Map<Class<?>, ClassSafeListener> classSafeListenerMap = new HashMap<Class<?>, ClassSafeListener>();
    private ParallelMode parallelMode = ParallelMode.CLASSES;

    @Override
    public void onStart(ITestContext testContext) {
        this.parallelMode = getParallelMode(testContext);
        synchronized (lockObject) {
            if (!effectiveInstanceInitialized) {
                effectiveInstance = true;
                effectiveInstanceInitialized = true;
            }
        }

        // This shouldn't be necessary really
        if (effectiveInstance)  {
            classSafeListenerMap.clear();
        }
    }

    private ParallelMode getParallelMode(ITestContext testContext) {
        final ISuite suite = testContext.getSuite();
        final ParallelMode parallelMode;
        if (suite.getParallel().equalsIgnoreCase("classes")) {
            parallelMode = ParallelMode.CLASSES;
        } else if (suite.getParallel().equalsIgnoreCase("methods")) {
            parallelMode = ParallelMode.METHODS;
        } else if (suite.getParallel().equalsIgnoreCase("tests")) {
            parallelMode = ParallelMode.TESTS;
        } else { // "false"
            parallelMode = ParallelMode.SEQUENTIAL;

        }
        return parallelMode;
    }



    @Override
    public void onFinish(ITestContext testContext) {
        if (effectiveInstance) {
            effectiveInstance = false;
            effectiveInstanceInitialized = false;
        }
    }

    public void beforeInvocation(IInvokedMethod method, ITestResult testResult) {
        if (!effectiveInstance) {
            return;
        }
        final IClass testNgClass = testResult.getTestClass();
        synchronized (this) {
            if (classSafeListenerMap.get(testNgClass.getRealClass()) == null) {
                classSafeListenerMap.put(testNgClass.getRealClass(), new ClassSafeListener());
            }
        }
        classSafeListenerMap.get(testNgClass.getRealClass()).beforeInvocation(method, testResult);
    }



    public void afterInvocation(IInvokedMethod method, ITestResult testResult) {
    }

    public class ClassSafeListener {
        private Bootstrapper bootStrapper = null;
        private boolean beforeClassInvoked = false;

        private IInvokedMethod lastMethod;

        public void beforeInvocation(final IInvokedMethod method, final ITestResult testResult) {
            final ITestClass testNgClass = method.getTestMethod().getTestClass();

            doStart(method,testResult);
            if (isBeforeClassMethod(method)) {
                doBeforeClass(testResult);
            }


            if (isBeforeTestConfigMethod(method)) {
                if (!hasBeforeClassMethods(method.getTestMethod().getTestClass())) {
                    doBeforeClass(testResult);
                }
                doBeforeTestConfigMethod(testResult);
            }

            if (method.isTestMethod()) {
                if (!hasBeforeTestConfigMethods(testNgClass)) {
                    if (!hasBeforeClassMethods(testNgClass)) {
                        doBeforeClass(testResult);
                    }
                    doBeforeTestConfigMethod(testResult);
                }

                AssertInternal.notNull(bootStrapper, "Bootstrapper is null!!");

                bootStrapper.beforeTestMethod(new Context(testResult.getInstance(), method.getTestMethod().getMethod()));
            }

            lastMethod = method;
        }

        private synchronized void doStart(final IInvokedMethod method, final ITestResult testResult) {
            if (bootStrapper == null) {
                final IClass testNgClass = testResult.getTestClass();
                this.bootStrapper = BootstrapperFactory.create(testNgClass.getRealClass());
                bootStrapper.setParallelMode(parallelMode);
                AssertInternal.notNull(bootStrapper, "Bootstrapper is null!!");
            }
        }


        private boolean hasBeforeClassMethods(final ITestClass testNgClass) {
            return testNgClass.getBeforeClassMethods().length > 0;
        }

        private boolean isBeforeClassMethod(final IInvokedMethod method) {
            return method.getTestMethod().isBeforeClassConfiguration();
        }

        private boolean hasBeforeTestConfigMethods(final ITestClass testNgClass) {
            return testNgClass.getBeforeTestMethods().length > 0;
        }

        private boolean isBeforeTestConfigMethod(final IInvokedMethod method) {
            return method.getTestMethod().isBeforeMethodConfiguration();
        }



        public void afterInvocation(IInvokedMethod method, ITestResult testResult) {
            // no-op
        }

        private synchronized void doBeforeClass(final ITestResult testResult) {
            if (!beforeClassInvoked) {
                final IClass testNgClass = testResult.getTestClass();

                if (lastMethod == null || !isBeforeClassMethod(lastMethod)) {
                    AssertInternal.notNull(bootStrapper, "bootstrapper is null");
                    bootStrapper.beforeClass(new Context(testResult.getInstance()));
                    beforeClassInvoked = true;
                }
            }
        }

        private void doBeforeTestConfigMethod(final ITestResult testResult) {
            final IClass testNgClass = testResult.getTestClass();
            if (lastMethod == null ||!isBeforeTestConfigMethod(lastMethod)) {
                AssertInternal.notNull(bootStrapper, "bootstrapper is null");
                bootStrapper.beforeTestConfiguration(new Context(testResult.getInstance()));
            }
        }
    }
}